﻿//
// Copyright (c) 2011-2012, Franz Haring
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//

using System;
using System.Collections.Generic;
using System.Reflection;

using DiscUtils;
using DiscUtils.Vfs;

using de.itools.opensource.utils;

namespace IsoReader {

  /// <summary>
  /// Class for the DiscUtils based IsoReader command line program.
  /// </summary>
  class Program
  {
    static void Main(string[] args) {
      int nExitCode=0;
      // Detect application name and version.
      string sAppFullName=Assembly.GetExecutingAssembly().Location;
      string sAppVersion=Assembly.GetEntryAssembly().GetName().Version.ToString();
      // Analyse command line.
      CommandLine commandLine = new CommandLine(sAppFullName,args,true);
      try {
        if ((commandLine.Items.Length<1 && commandLine.GetOption("lic")==null) || commandLine.GetOption("?")!=null || commandLine.GetOption("h")!=null || commandLine.GetOption("help")!=null) {
          // Show calling conventions.
          Console.Out.WriteLine(
            "IsoReader Version "+sAppVersion+"\r\n"+
            "\r\n" +
            "  A command line utility program to extract files from ISO formatted\r\n"+
            "  CD or DVD images. .NET 2.0 or higher is required.\r\n" +
            "\r\n" +
            "  ISO-9660 and ISO-13346 (UDF) file systems will be recognised.\r\n" +
            "\r\n" +
            "Calling conventions:\r\n" +
            "\r\n" +
            "IsoReader SourceFile [DestinationPath] [Options]\r\n" +
            "\r\n" +
            "SourceFile:          File name with absolute or relative path of the ISO\r\n" +
            "                     source file.\r\n"+
            "\r\n" +
            "DestinationPath:     Destination folder for the extracted files and\r\n"+
            "                     directories. In this folder a subfolder with the\r\n"+
            "                     name of the ISO file (but without the .iso extension)\r\n"+
            "                     will be created. If not specified, this subfolder will\r\n"+
            "                     be created in the current directory.\r\n"+
            "\r\n" +
            "Options:\r\n" +
            "\r\n" +
            "-l or -list          The file and directory names are only listed to\r\n"+
            "                     the command line.\r\n" +
            "\r\n" +
            "-lic                 Display licensing information.\r\n" +
            "\r\n" +
            "-p                   Pause at the end until user hits a key.\r\n"+
            "\r\n" +
            "-? or -h or -help    Display this information.\r\n" +
            "");
          nExitCode=1;
        }
        if (commandLine.GetOption("lic")!=null) {
          // Show licensing information.
          Console.Out.WriteLine(
            "IsoReader licensing information:\r\n"+
            "\r\n" +
            "            Copyright 2011-2012 Franz Haring (fh@informeon.de).\r\n" +
            "            \r\n" +
            "            The program may be used, changed and redistributed free of\r\n" +
            "            charge under the conditions of the MIT license (see below).\r\n" +
            "            \r\n" +
            "            The program uses the DiscUtils library, copyrighted by\r\n" +
            "            Kenneth Bell, available on http://discutils.codeplex.com.\r\n" +
            "            \r\n" +
            "            The program icon is based on CD_icon_test.svg by Tkgd2007,\r\n" +
            "            available on\r\n" +
            "            http://commons.wikimedia.org/wiki/File:CD_icon_test.svg.\r\n" +
            "\r\n" +
            "\r\n" +
            "The MIT license:\r\n" +
            "\r\n" +
            "Permission is hereby granted, free of charge, to any person \r\n"+
            "obtaining a copy of this software and associated documentation \r\n"+
            "files (the 'Software'), to deal in the Software without \r\n"+
            "restriction, including without limitation the rights to use, \r\n"+
            "copy, modify, merge, publish, distribute, sublicense, and/or \r\n"+
            "sell copies of the Software, and to permit persons to whom the \r\n"+
            "Software is furnished to do so, subject to the following \r\n"+
            "conditions:\r\n"+
            "\r\n"+
            "The above copyright notice and this permission notice shall be \r\n"+
            "included in all copies or substantial portions of the Software.\r\n"+
            "\r\n"+
            "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, \r\n"+
            "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES \r\n"+
            "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND \r\n"+
            "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT \r\n"+
            "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, \r\n"+
            "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING \r\n"+
            "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR \r\n"+
            "OTHER DEALINGS IN THE SOFTWARE.\r\n"+
            "");
        }
        if (commandLine.Items.Length >= 1) {
          // Read ISO file.
          bool bSuccess=false;
          string sIsoFile=commandLine.Items[0];
          string sDestinationRootPath=(commandLine.GetOption("l")!=null || commandLine.GetOption("list")!=null)?null:(commandLine.Items.Length<2)?".":commandLine.Items[1];
          if (System.IO.File.Exists(sIsoFile)) {
            ReadIsoFile(sIsoFile,sDestinationRootPath,out bSuccess);
          } else {
            Console.Out.WriteLine("Source file '"+sIsoFile+"' not found.");
          }
          nExitCode=bSuccess?0:2;
        }
      } catch (Exception ex) {
        Console.Out.WriteLine(ex.ToString());
        nExitCode=2;
      } finally {
        if (commandLine.GetOption("p",null)!=null) {
          System.Console.Write("Press any key..");
          System.Console.ReadKey(true);
        }
      }
      Environment.Exit(nExitCode);
    }

    public static void ReadIsoFile(string sIsoFile,string sDestinationRootPath,out bool bSuccess) {
      bSuccess=true;
      System.IO.Stream streamIsoFile=null;
      try {
        streamIsoFile=new System.IO.FileStream(sIsoFile,System.IO.FileMode.Open);
        FileSystemInfo[] fsia=FileSystemManager.DetectDefaultFileSystems(streamIsoFile);
        if (fsia.Length<1) {
          bSuccess=false;
          Console.Out.WriteLine("No valid disc file system detected.");
        } else {
          DiscFileSystem dfs=fsia[0].Open(streamIsoFile);
          if (sDestinationRootPath!=null) {
            sDestinationRootPath=System.IO.Path.Combine(sDestinationRootPath,System.IO.Path.GetFileNameWithoutExtension(sIsoFile));
            if (System.IO.Directory.Exists(sDestinationRootPath)) {
              System.IO.Directory.Delete(sDestinationRootPath,true);
            }
          }
          Console.Out.Write("Reading file..");
          ReadIsoFolder(dfs,@"",sDestinationRootPath,ref bSuccess);
          Console.Out.WriteLine("completed.");
          return;
        }
      } finally {
        if (streamIsoFile!=null) {
          streamIsoFile.Close();
        }
      }
    }

    public static void ReadIsoFolder(DiscFileSystem cdReader,string sIsoPath,string sDestinationRootPath,ref bool bSuccess) {
      try {
        string[] saFiles=cdReader.GetFiles(sIsoPath);
        foreach (string sFile in saFiles) {
          if (sDestinationRootPath==null) {
            Console.Out.WriteLine("\t"+sFile);
          } else {
            DiscFileInfo dfiIso=cdReader.GetFileInfo(sFile);
            string sDestinationPath=System.IO.Path.Combine(sDestinationRootPath,dfiIso.DirectoryName.Substring(0,dfiIso.DirectoryName.Length-1));
            if (!System.IO.Directory.Exists(sDestinationPath)) {
              System.IO.Directory.CreateDirectory(sDestinationPath);
            }
            string sDestinationFile=System.IO.Path.Combine(sDestinationPath,dfiIso.Name);
            SparseStream streamIsoFile=cdReader.OpenFile(sFile,System.IO.FileMode.Open);
            System.IO.FileStream fsDest=new System.IO.FileStream(sDestinationFile,System.IO.FileMode.Create);
            byte[] baData=new byte[0x4000];
            while (true) {
              int nReadCount=streamIsoFile.Read(baData,0,baData.Length);
              if (nReadCount<1) {
                break;
              } else {
                fsDest.Write(baData,0,nReadCount);
              }
              // Every two seconds a dot to signalize work is in progress.
              int nMs=Environment.TickCount;
              if (nMsSinceLastDot==0 || nMs-nMsSinceLastDot>=2000) {
                Console.Out.Write(".");
                nMsSinceLastDot=nMs;
              }
            }
            streamIsoFile.Close();
            fsDest.Close();
          }
        }
        string[] saDirectories=cdReader.GetDirectories(sIsoPath);
        foreach (string sDirectory in saDirectories) {
          if (sDestinationRootPath==null) {
            Console.Out.WriteLine(sDirectory);
          }
          ReadIsoFolder(cdReader,sDirectory,sDestinationRootPath,ref bSuccess);
        }
        return;
      } catch (Exception ex) {
        Console.Out.WriteLine(ex.ToString());
      }
      bSuccess=false;
    }

    private static int nMsSinceLastDot=0;

  }

}
